package cn.jolyoulu.common.poi.excel.writer.buffer;

import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import cn.jolyoulu.common.poi.excel.annotation.ExcelExport;

import java.io.*;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @Author: JolyouLu
 * @Date: 2023/12/17 18:14
 * @Description Excel写入具
 */
@Slf4j
public class BufferExcelWriter<T> {

    /**
     * 带缓存的工作簿
     */
    private SXSSFWorkbook wb;

    /**
     * 当前操作的sheet
     */
    private SXSSFSheet sheet;

    /**
     * 需要处理的数据类型
     */
    protected Class<T> dataType;
    /**
     * 数据类型中的字段
     */
    protected Map<Integer, Field> fieldMap;
    /**
     * 开始写入数据的位置
     */
    protected Integer startRow;

    private BufferExcelWriter(Class<T> dataType, int buffSize) {
        this.wb = new SXSSFWorkbook(buffSize);
        this.dataType = dataType;
        this.fieldMap = getFieldMapOfExcelExport(dataType);
    }

    /**
     * 初始化一个BufferExcelWriter，指定缓存行数量，在每一轮写入中当写入行等于buffSize后会将数据刷盘入硬盘
     *
     * @param dataType 需要识别的类
     * @param buffSize 指定缓存行数量
     * @return BufferExcelWriter
     */
    public static <T> BufferExcelWriter<T> of(Class<T> dataType, int buffSize) {
        return new BufferExcelWriter<>(dataType, buffSize);
    }

    /**
     * 初始化一个BufferExcelWriter
     *
     * @param dataType 需要识别的类
     * @return BufferExcelWriter
     */
    public static <T> BufferExcelWriter<T> of(Class<T> dataType) {
        return BufferExcelWriter.of(dataType, 100);
    }

    /**
     * 加载一个Excel模板文件，只能加载xlsx格式的
     * 加载模板后之前生成的excel将会被丢弃
     * OPCPackage.open(new FileInputStream("/temp.xlsx"))
     * @param tempFile 模板文件
     * @return BufferExcelWriter
     */
    public BufferExcelWriter<T> load(OPCPackage tempFile) throws IOException {
        this.wb.dispose();
        this.wb = new SXSSFWorkbook(new XSSFWorkbook(tempFile));
        return this;
    }

    /**
     * 改变写入数据类型，通常用于切换其他Sheet时写入其他格式的数据时使用
     *
     * @param dataType 切换数据类型
     * @return BufferExcelWriter
     */
    public <T> BufferExcelWriter<T> changeDataType(Class<T> dataType) {
        BufferExcelWriter<T> excelWriter = new BufferExcelWriter<>(dataType, 100);
        excelWriter.sheet = this.sheet;
        excelWriter.wb = this.wb;
        return excelWriter;
    }


    /**
     * 切换sheet，没有时会创建一个
     *
     * @param sheetName sheet的名称
     * @param startRow  开始写入数据的行号，1开始
     * @return BufferExcelWriter
     */
    public BufferExcelWriter<T> sheet(String sheetName, int startRow) {
        this.startRow = startRow;
        this.sheet = wb.getSheet(sheetName);
        if (Objects.isNull(sheet)) { //空的工作表时需要创建表头
            this.sheet = wb.createSheet(sheetName);
            createHeader(this.sheet);
        }
        return this;
    }

    /**
     * 切换sheet，没有时会创建一个
     *
     * @param sheetName sheet的名称
     * @return BufferExcelWriter
     */
    public BufferExcelWriter<T> sheet(String sheetName) {
        sheet(sheetName,1);
        return this;
    }


    /**
     * 创建表头
     *
     * @param sheet
     */
    private void createHeader(SXSSFSheet sheet) {
        //获取第一行，如果没有表头就初始化一个
        SXSSFRow sheetRow = sheet.getRow(0);
        if (Objects.nonNull(sheetRow)) {
            return;
        }
        sheetRow = sheet.createRow(0);
        for (Map.Entry<Integer, Field> entry : fieldMap.entrySet()) {
            Integer num = entry.getKey() - 1;
            if (Objects.isNull(sheetRow.getCell(num))) {
                Field field = entry.getValue();
                SXSSFCell cell = sheetRow.createCell(num, CellType.STRING);
                ExcelExport annotation = field.getAnnotation(ExcelExport.class);
                cell.setCellValue(annotation.title());
            }
        }
    }

    /**
     * 写入数据
     *
     * @param data
     */
    public BufferExcelWriter<T> write(List<T> data) {
        try {
            //如果没有直接写就创建一个
            if (Objects.isNull(sheet)) {
                sheet = wb.createSheet();
            }
            //空数据不做处理
            if (Objects.isNull(data) || data.isEmpty()) {
                return this;
            }
            //添加数据
            for (T item : data) {
                SXSSFRow row = sheet.createRow(startRow);
                startRow++;
                for (Map.Entry<Integer, Field> entry : fieldMap.entrySet()) {
                    Integer num = entry.getKey() - 1;
                    Field field = entry.getValue();
                    SXSSFCell cell = row.createCell(num);
                    Object value = field.get(item);
                    if (Objects.nonNull(value)) {
                        cell.setCellValue(value.toString());
                    }
                }
            }
        } catch (Exception e) {
            log.error("BufferExcelWriter写入数据异常", e);
        }
        return this;
    }

    /**
     * 保存为文件，保存完毕后无法再次写入了
     *
     * @param path     文件路径
     * @param fileName 文件名称
     */
    public void saveAsFile(String path, String fileName) {
        try (FileOutputStream out = new FileOutputStream(path + fileName + ".xlsx")) {
            wb.write(out);
            wb.dispose();
        } catch (FileNotFoundException e) {
            throw new RuntimeException("文件未找到！");
        } catch (IOException e) {
            throw new RuntimeException("IO异常");
        }
    }

    /**
     * 保存到流中，保存完毕后无法再次写入了
     * @param outputStream 输出流
     */
    public void saveAsStream(OutputStream outputStream) {
        try{
            wb.write(outputStream);
            wb.dispose();
        } catch (FileNotFoundException e) {
            throw new RuntimeException("文件未找到！");
        } catch (IOException e) {
            throw new RuntimeException("IO异常");
        }
    }

    /**
     * 获取类中包含的ExcelImport信息
     *
     * @param dataType
     * @return
     */
    protected Map<Integer, Field> getFieldMapOfExcelExport(Class<T> dataType) {
        //获取类中的所有属性
        Field[] fields = dataType.getDeclaredFields();
        if (Objects.isNull(fields) || fields.length <= 0) {
            return Collections.emptyMap();
        }
        HashMap<Integer, Field> res = new HashMap<>();
        for (Field field : fields) {
            ExcelExport annotation = field.getAnnotation(ExcelExport.class);
            if (Objects.isNull(annotation)) {
                continue;
            }
            int titleIndex = annotation.titleIndex();
            if (res.containsKey(titleIndex)) {
                throw new RuntimeException("ExcelExport => titleIndex 不能重复");
            }
            if (titleIndex <= 0) {
                throw new RuntimeException("ExcelExport => titleIndex 必须>0");
            }
            field.setAccessible(true);
            res.put(annotation.titleIndex(), field);
        }
        return res;
    }
}
